﻿using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Minio;
using Minio.DataModel;
using Minio.DataModel.Args;
using Minio.DataModel.Encryption;
using Minio.Exceptions;
using PmSoft;
using PmSoft.Exceptions;

namespace Modules.Minio
{
    [UseDI(ServiceLifetime.Scoped)]
    public class MinIoRepository : IDisposable
    {
        private readonly IConfiguration configuration;
        private readonly IMinioClient client;
        public MinIoRepository(IConfiguration configuration)
        {

            this.configuration = configuration;
            string? endPoint = configuration.GetValue<string>("ThreePartyOptions:Minio:EndPoint");
            string? accessKey = configuration.GetValue<string>("ThreePartyOptions:Minio:AccessKey");
            string? secretKey = configuration.GetValue<string>("ThreePartyOptions:Minio:SecretKey");

            bool withSSL = configuration.GetValue("ThreePartyOptions:Minio:WithSSL", false);
            this.client = new MinioClient()
                                 .WithEndpoint(endPoint)
                                 .WithCredentials(accessKey,
                                          secretKey);
            if (withSSL)
                client = client.WithSSL();
            this.client.Build();
        }


        #region 私有方法

        /// <summary>
        /// 创建存储桶
        /// 
        /// <example >
        ///     <code >
        /// MinioHelper.MakeBucket(minio, buckName).Wait();    
        ///     </code>
        /// 
        /// </example>
        /// </summary>
        /// <param name = "minio" > 连接实例 </param >
        /// <param name="bucketName">存储桶名称</param>
        /// <param name = "loc" > 可选参数 </param >
        /// <returns ></returns >
        private async Task MakeBucketAsync(string bucketName, string loc = "us-east-1")
        {
            bool found = await BucketExistsAsync(bucketName);
            if (found)
            {
                throw new BusinessException(string.Format("存储桶[{0}]已存在", bucketName));
            }

            MakeBucketArgs args = new MakeBucketArgs()
                                   .WithBucket(bucketName)
                                   .WithLocation(loc);

            await client.MakeBucketAsync(args);
        }

        /// <summary>
        /// 校验是否存在,如果不存在则报错
        /// <example>
        /// 调用示例
        /// <code>
        /// bool exists = MinioHelper.BucketExists(minio, buckName);
        /// </code>
        /// </example>
        /// </summary>
        /// <param name="minio"></param>
        /// <param name="bucketName"></param>
        /// <exception cref="Exception"></exception>
        private async Task CheckBucketAsync(string bucketName)
        {
            bool found = await BucketExistsAsync(bucketName);
            if (!found)
            {
                throw new BusinessException(string.Format("存储桶[{0}]不存在", bucketName));
            }
        }

        /// <summary>
        /// 检查存储桶是否存在
        /// </summary>
        /// <example>
        /// <code>
        /// var data = MinioHelper.ListBuckets(minio);
        /// </code>
        /// </example>
        /// <param name="minio">连接实例</param>
        /// <param name="bucketName">存储桶名称</param>
        /// <returns></returns>
        private async Task<bool> BucketExistsAsync(string bucketName, CancellationToken cancellationToken = default(CancellationToken))
        {
            BucketExistsArgs args = new BucketExistsArgs()
                                            .WithBucket(bucketName);
            return await client.BucketExistsAsync(args);
        }

        #endregion


        /// <summary>生成一个给HTTP GET请求用的presigned URL。浏览器/移动端的客户端可以用这个URL进行下载，即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间，默认值是7天。
        /// 生成一个给HTTP GET请求用的presigned URL。浏览器/移动端的客户端可以用这个URL进行下载，即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间，默认值是7天。
        /// </summary>
        /// <param name="minio">连接实例</param>
        /// <param name="bucketName">存储桶名称</param>
        /// <param name="objectName">存储桶里的对象名称</param>
        /// <param name="expiresInt">失效时间（以秒为单位），默认是7天，不得大于七天</param>
        /// <param name="reqParams">额外的响应头信息，支持response-expires、response-content-type、response-cache-control、response-content-disposition</param>
        /// <returns></returns>
        public async Task<string> PresignedGetObject( string bucketName, string objectName, int expiresInt = 1000)
        {
            //await CheckBucketAsync(bucketName);

            try
            {
                //Dictionary<string, string> reqParams = new Dictionary<string, string> { { "response-content-type", "application/json" } };
                PresignedGetObjectArgs args = new PresignedGetObjectArgs()
                                                        .WithBucket(bucketName)
                                                        .WithObject(objectName)
                                                        //.WithHeaders(reqParams)
                                                        .WithExpiry(expiresInt);

                //.WithRequestDate(DateTime.Now.ToUniversalTime());

                return await client.PresignedGetObjectAsync(args);
            }
            catch (Exception e)
            {
                throw new BusinessException(e.Message);
            }
        }

        /// <summary>通过Stream上传对象
        /// 通过Stream上传对象
        /// </summary>
        /// <param name="bucketName">存储桶名称</param>
        /// <param name="objectName">存储桶里的对象名称</param>
        /// <param name="data">要上传的Stream对象</param>
        /// <param name="size">流的大小</param>
        /// <param name="contentType">文件的Content type，默认是"application/octet-stream"</param>
        /// <param name="metaData">元数据头信息的Dictionary对象，默认是null</param>
        /// <returns></returns>
        public async Task PutObjectAsync(string bucketName, string objectName, Stream data, long size, string contentType = "application/octet-stream", Dictionary<string, string>? metaData = null)
        {
            //await CheckBucketAsync(bucketName);
            PutObjectArgs args = new PutObjectArgs()
                                        .WithBucket(bucketName)
                                        .WithObject(objectName)
            .WithStreamData(data)
                                        .WithObjectSize(size)
                                        .WithContentType(contentType)
                                        .WithHeaders(metaData)
                                        .WithServerSideEncryption(null);
            await client.PutObjectAsync(args);
        }

        /// <summary>下载对象指定区域的字节数组做为流。offset和length都必须传
        /// 下载对象指定区域的字节数组做为流。offset和length都必须传
        /// </summary>
        /// <param name="minio">连接实例</param>
        /// <param name="bucketName">存储桶名称</param>
        /// <param name="objectName">存储桶里的对象名称</param>
        /// <param name="offset">offset 是起始字节的位置</param>
        /// <param name="length">length是要读取的长度</param>
        /// <param name="callback">处理流的回调函数</param>
        /// <returns></returns>
        public async Task GetObjectAsync(string bucketName, string objectName, long offset, long length, Action<Stream> callback)
        {
            await CheckBucketAsync(bucketName);

            try
            {
                StatObjectArgs args = new StatObjectArgs()
                                        .WithBucket(bucketName)
                                        .WithObject(objectName)
                                        .WithServerSideEncryption(null);

                await client.StatObjectAsync(args);

                GetObjectArgs objArgs = new GetObjectArgs()
                                        .WithBucket(bucketName)
                                        .WithObject(objectName)
                                        .WithCallbackStream(callback)
                                        .WithOffsetAndLength(offset, length)
                                        .WithServerSideEncryption(null);
                await client.GetObjectAsync(objArgs);
            }
            catch (MinioException e)
            {
                throw new BusinessException(e.Message);
            }
        }

        /// <summary>
        /// 从桶下载文件到流
        /// </summary>
        /// <param name="bucketName">存储桶名称</param>
        /// <param name="objectName">存储桶里的对象名称</param>
        /// <param name="sse"></param>
        /// <returns></returns>
        public async Task<ObjectStat> FGetObjectAsync(string bucketName, string objectName, IServerSideEncryption sse = null, Action<Stream> cb = null)
        {
            //await CheckBucketAsync(bucketName);

            try
            {

                GetObjectArgs args = new GetObjectArgs()
                                        .WithBucket(bucketName)
                                        .WithObject(objectName)
                                        .WithServerSideEncryption(sse)
                                        .WithCallbackStream(cb);
                return await client.GetObjectAsync(args);
            }
            catch (MinioException e)
            {
                throw new BusinessException(e.Message);
            }
        }

        public void Dispose()
        {
            if (this.client != null)
                this.client.Dispose();
        }
    }
}
